#TODO:不同平台可能SDK路径不同
sdk_path="D:/asdk"
android_version="30"
asdk_version="30.0.3"#{sdk_path}/build-tools/下的版本号
name="FKASGradle"#项目名，对应adb，CMakeLists和C/C++
appname="应用名"
app_attr={"label":appname,"allowBackup":"true"}#<application>的属性
package=f"com/{name}"#对应AndroidManifest.xml和{name}.java，即入口文件 MainActivity.java必须存在
p=".".join(package.split("/"))
activity_attr={
    "name":f"{p}.MainActivity",#勿动
    "metadata":{},#单独项
    "intent":{
        "action":"android.intent.action.MAIN",
        "category":"android.intent.category.LAUNCHER"
    },#单独项
    "label":f"{appname}",
    "launchMode":"singleTask"
}#MainActivity的属性
keyname="mfxt"
request_permisson=["INTERNET"]#可选权限 不影响除授权外程序的正常运行
must_permission=["WRITE_EXTERNAL_STORAGE"]#必要权限，参考安卓官方开发文档
uses_feature=[]#使用的硬件特性
exe_suffix=".exe"

ndk_path="D:/asdk/ndk/25.1.8937393"
#armeabi-v7a arm64-v8a x86 x86_64
aabi="armeabi-v7a"
debug=True
compiler_path="toolchains/llvm/prebuilt/windows-x86_64/bin"
ndk_cpp_version="v1"#对应include/c++/{ndk_cpp_version}
cmake_version="3.22.1"
use_library_name={"lua"}#其它CMake(add_library)的库名 只需要写用到的

#需要编辑的结束

file_library_name:set[str]=set()
for v in use_library_name:
    file_library_name.add(f"lib{v}.so")
file_library_name.add(f"lib{name}.so")

asm_dir={"armeabi-v7a":"arm-linux-androideabi",
"arm64-v8a":"aarch64-linux-android","x86_64":"x86_64-linux-android",
"x86":"i686-linux-android"}
import os
import sys
argv=sys.argv
path=os.path
mkdir=os.mkdir
chdir=os.chdir
system=os.system
exists=path.exists
listdir=os.listdir
isdir=os.path.isdir
pak="_".join(package.split("/"))

android_config=False#res/ AndroidManifest.xml R.java
java_src=False#src/java/
c_src=False#src/*.cpp *.c
cmake=False
lib_path=f"lib/{aabi}/"

def copyso(start:str)->None:
    for s in listdir(start):
        cur=f"{start}/{s}"
        if isdir(cur):
            copyso(cur)
            continue
        if s.endswith(".so"):
            if s in file_library_name:
                output=f"{lib_path}{s}"
                if output!=cur:
                    with open(output,"wb") as f:
                        with open(cur,"rb") as g:
                            f.write(g.read())

def printhelp()->None:
    print("FKASGradle build script 1.3 by MFXT(魔凤啸天)")
    print("build.py option1 option2 ....")
    print("options:")
    print("'-android' for res/ AndroidManifest.xml R.java")
    print("'-java' for src/ and include/jniheader/ (need '-c')")
    print("'-c' for src/*.cpp src/*.c")
    print("'-cmake' for CMakeLists.txt")
    print("'-all' for all above")
    print("'-adb' for adb install script only, no others")
    print("'-vscode' for vscode local config only")
    print("example usage: -java -c -vscode")
    print("'-help' for this help")

if not exists("./src"):
    mkdir("src")
if not exists("./lib"):
    mkdir("lib")
if not exists("./build"):
    mkdir("build")

for opt in argv:
    if opt[0]!='-':
        continue
    if opt=="-cmake":
        cmake=True
        continue
    if opt=="-android":
        android_config=True
        continue
    if opt=="-java":
        java_src=True
        continue
    if opt=="-c":
        c_src=True
        continue
    if opt=="-all":
        android_config=True
        java_src=True
        c_src=True
        cmake=True
        continue
    if opt=="-adb":
        s="import os\n"
        s+=f"os.system('{sdk_path}/platform-tools/adb{exe_suffix} install ./build/bin/{appname}.apk')\n"
        with open("./install.py","w") as f:
            f.write(s)
        exit(0)
    if opt=="-help":
        printhelp()
        exit(0)
    if opt=="-vscode":
        if not exists("./.vscode"):
            mkdir(".vscode")
        if java_src:
            output='{"java.project.sourcePaths": ["${workspaceFolder}/src/java"],'+\
            f'"java.project.outputPath": "${"workspaceFolder/build/"+package}",'+\
            f'"java.project.referencedLibraries": ['+\
            '"${workspaceFolder}/lib/java/*.jar",'+\
            f'"{sdk_path}/platforms/android-{android_version}/android.jar"]'+"}"
            with open("./.vscode/settings.json","w") as f:
                f.write(output)
        if c_src:
            output='{"configurations":[{'+\
            '"name":"Android","includePath":['+\
            f'"{ndk_path}/sources",'+\
            f'"{ndk_path}/{compiler_path}/../sysroot/usr/include",'+\
            f'"{ndk_path}/{compiler_path}/../sysroot/usr/include/c++/{ndk_cpp_version}",'+\
            f'"{ndk_path}/{compiler_path}/../sysroot/usr/include/{asm_dir[aabi]}",'
            for s in listdir("./lib/cpp",):
                output+='"${workspaceFolder}/lib/cpp/'+s+'/include",'
            output+='"${workspaceFolder}/include"],'+\
            f'"compilerPath":"{ndk_path}/{compiler_path}/clang{exe_suffix}",'+\
            '"cStandard":"c11","cppStandard":"c++17","intelliSenseMode":"clang-x64",'+\
            '"configurationProvider":"clang","defines":["ANDROID"]}],"version": 4}'
            with open("./.vscode/c_cpp_properties.json","w") as f:
                f.write(output)
        if java_src or c_src:
            exit(0)
    print(f"invalid option {opt}")
    exit(-1)
if not java_src and not c_src and not android_config and not cmake:
    printhelp()
    exit(0)
if android_config:
    #manifest
    output="<?xml version='1.0'?>\n<manifest xmlns:a='http://schemas.android.com/apk/res/android' \n"+\
    f"package='{p}' a:versionCode='0' a:versionName='0'>\n"
    for s in request_permisson:
        output+=f'<permission a:name="android.permission.{s}"/>\n'
    for s in must_permission:
        output+=f'<uses-permission a:name="android.permission.{s}"/>\n'
    for feature in uses_feature:
        output+="<uses-feature "
        for k in feature:
            output+=f"a:{k}='{feature[k]}' "
        output+="/>\n"
    output+=f"<uses-sdk a:targetSdkVersion='{android_version}'/>\n<application\n"
    intent_filter=activity_attr.pop("intent")
    metadata=activity_attr.pop("metadata")
    for k in app_attr:
        output+=f"a:{k}='{app_attr[k]}'\n"
    output+=f"><activity\n"
    for k in activity_attr:
        output+=f"a:{k}='{activity_attr[k]}'\n"
    output+="><intent-filter>\n"
    for intent in intent_filter:
        output+=f"<{intent} a:name='{intent_filter[intent]}'/>\n"
    output+="</intent-filter>\n"
    for k in metadata:
        output+=f'<meta-data a:name="{k}" a:value="{metadata[k]}"/>\n'
    output+="</activity>\n</application>\n</manifest>"
    with open("./AndroidManifest.xml","w",encoding="utf-8") as f:
        f.write(output)
cmake_command=""
bins=f'{sdk_path}/build-tools/{asdk_version}/aapt add ./bin/{appname}.unaligned.apk classes.dex'
chdir("./build")
if android_config:
    if not system(
    f'{sdk_path}/build-tools/{asdk_version}/aapt package -f -m -J ./ '+
    "-M ../AndroidManifest.xml "+
    f"-S ../res -I {sdk_path}/platforms/android-{android_version}/android.jar"):
        print("R.java Generation")
    else:
        print("R.java Generation failed")
        exit(-1)
if not exists("./obj"):
    mkdir("obj")
if java_src:
    javac_command=f'javac -d obj -cp {sdk_path}/platforms/android-{android_version}/android.jar;../lib/java/* '
    if debug:
        javac_command+="-g "
    if c_src:
        if not exists("../include"):
            chdir("..")
            mkdir("include")
            chdir("./include")
            if not exists("./jniheader"):
                mkdir("jniheader")
            chdir("../build")
        javac_command+="-h ../include/jniheader "
    javac_command+=f"./{package}/R.java ../src/java/*"
    if not system(javac_command):
        print("java Compiled")
        if c_src:
            source=""
            for p in listdir("../include/jniheader"):
                header=f"jniheader/{p}"
                source=f'#include "{header}"\n\n'
                with open(f"../include/{header}","r") as f:
                    s=f.read()
                    index=0
                    while True:
                        found=s.find("JNIEXPORT",index)
                        if found!=-1:
                            index=found+10
                            v=s[index:s.find(';',index)]
                            index+=len(v)
                            source+=v+"{}\n\n"
                            continue
                        break
                output=f"../src/{p.split('.')[0]}.cpp"
                if not exists(output):
                    with open(output,"w") as f:
                        f.write(source)
    else:
        print("java Compile failed")
        exit(-1)
if not exists("./lib"):
    mkdir("lib")
chdir("./lib")
if not exists(f"./{aabi}"):
    mkdir(aabi)
chdir("..")
if not exists("./bin"):
    mkdir("bin")
if c_src:
    if cmake:
        cmake_text=f"project({name})\n"+\
        f"cmake_minimum_required(VERSION {cmake_version})\n"+\
        "set(JAVA_AWT_LIBRARY NotNeeded)\n"+\
        "set(JAVA_JVM_LIBRARY NotNeeded)\n"+\
        "set(JAVA_INCLUDE_PATH2 NotNeeded)\n"+\
        "set(JAVA_AWT_INCLUDE_PATH NotNeeded)\n"+\
        "find_package(JNI REQUIRED)\n"+\
        "include_directories(${JNI_INCLUDE_DIRS})\n"+\
        "include_directories(include)\n"
        for s in listdir("../lib/cpp"):
            cmake_text+=f"include_directories(lib/cpp/{s}/include)\n"+\
            f"add_subdirectory(lib/cpp/{s})\n"
        cmake_text+=f"aux_source_directory(src {name}_SRC)\n"+\
        f"add_library({name}"+" SHARED ${"+name+"_SRC})\n"
        if len(use_library_name):
            cmake_text+=f"target_link_libraries({name} {' '.join(use_library_name)})"
        with open("../CMakeLists.txt","w") as f:
            f.write(cmake_text)
        print("CMakeLists.txt Generation")
    for s in file_library_name:
        bins+=f" {lib_path}{s}"
    cmake_command=f"{sdk_path}/cmake/{cmake_version}/bin/cmake{exe_suffix} ../ "+\
    f"-DCMAKE_TOOLTRAIN_FILE={ndk_path}/build/cmake/android.toolchain.cmake "+\
    f"-DANDROID_TOOLCHAIN=clang -DANDROID_STL=c++_shared -DANDROID_ABI={aabi} "+\
    f"-DANDROID_CPP_FEATURES=exceptions "+\
    f"-DCMAKE_INSTALL_PREFIX=build/{aabi} -DANDROID_PLATFORM=android-{android_version} "+\
    f"-DANDROID_NDK={ndk_path} -DCMAKE_SYSTEM_NAME=Android "+\
    f"-DCMAKE_C_COMPILER={ndk_path}/{compiler_path}/clang{exe_suffix} "+\
    f"-DCMAKE_MAKE_PROGRAM={sdk_path}/cmake/{cmake_version}/bin/ninja "+\
    f"-DCMAKE_CXX_COMPILER={ndk_path}/{compiler_path}/clang++{exe_suffix} -GNinja "
    if debug:
        cmake_command+=" -DCMAKE_BUILD_TYPE=Debug"
    if not system(cmake_command):
        print("cmake configured")
    else:
        print("cmake configure error")
        exit(-1)
    if not system(f"{sdk_path}/cmake/{cmake_version}/bin/ninja"):
        print("native library built")
    else:
        print("native library build error")
        exit(-1)
    copyso(".")
if java_src:
    #fixed by 奔跑的小蜗牛
    output=f'{sdk_path}/build-tools/{asdk_version}/d8 '+\
    f'{sdk_path}/platforms/android-{android_version}/android.jar ./obj/{package}/*.class'
    if not debug:
        output+=" --release"
    if not system(output):
        print("Dex Generation")
    else:
        print("Dex Generation failed")
        exit(-1)

if not system(
f'{sdk_path}/build-tools/{asdk_version}/aapt package -f -m -F '+
f'./bin/{appname}.unaligned.apk -M ../AndroidManifest.xml '+
f'-S ../res -I {sdk_path}/platforms/android-{android_version}/android.jar'):
    print("unaligned apk Generation")
else:
    print("unaligned apk Generation failed")
    exit(-1)
if not system(bins):
    print("dex and so(if enabled) added")
else:
    print("dex and so(if enabled) add failed")
    exit(-1)
if exists(f"{keyname}.keystore"):
    print("key exists skip")
else:
    if not system(f'keytool -genkeypair -validity 365 -keystore {keyname}.keystore '+
    '-keyalg RSA -keysize 2048 '+
    '-dname "CN=MFXTBUILD, OU=MFXTBUILD, O=MFXTBUILD, L=MFXTBUILD, S=MFXTBUILD, C=MFXTBUILD"'):
        print("key Generation")
    else:
        print("key Generation failed")
        exit(-1)
if not system(
f'{sdk_path}/build-tools/{asdk_version}/zipalign -f 4 ./bin/{appname}.unaligned.apk ./bin/{appname}.apk'):
    print("zip alignment")
else:
    print("zip alignment failed")
    exit(-1)
if not system(f'{sdk_path}/build-tools/{asdk_version}/apksigner sign --ks {keyname}.keystore ./bin/{appname}.apk'):
    print("apk signed")
    exit(0)
else:
    print("apk sign failed")
    exit(-1)

'''
MIT License

Copyright (c) 2023 魔凤啸天

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
'''